
class Error(StandardError):
    __guid__ = 'slash.Error'

from service import *
import const
import sys
import os
import param
import blue
import chat
import uthread
import form
import triui
import xtriui
import uix
import trinity
import uiutil
import svc
import util
import log
import uiconst
import uicls
SERVICENAME = 'slash'
TOKEN = '$'
aliasFile = 'aliases.ini'
macroFile = 'macros.ini'
version = 2.4
Message = lambda title, body, icon = triui.INFO: sm.GetService('gameui').MessageBox(body, title, buttons=uix.OK, icon=icon)
AsyncMessage = lambda *args: uthread.new(Message, *args)
Progress = lambda title, text, current, total: sm.GetService('loading').ProgressWnd(title, text, current, total)

class InlineFunctionResolver:

    def __getitem__(self, func):
        if (':' in func):
            (func, args,) = func.split(':', 1)
        else:
            args = ()
        method = getattr(self, ('f_' + func.lower()), None)
        if (func is None):
            raise Error(("unknown inline function '%s'" % func))
        if args:
            args = args.split(',')
        return method(*args)



    def _getinvrow(self, *args):
        if (len(args) == 1):
            containerID = eve.session.shipid
            flag = args[0]
        elif (len(args) == 2):
            if (args[0].lower() == 'me'):
                containerID = eve.session.shipid
            else:
                containerID = int(args[0])
            flag = args[1]
        flag = getattr(const, ('flag' + flag.capitalize()))
        for rec in eve.GetInventoryFromId(containerID).List():
            print rec
            if (rec.flag == flag):
                return rec




    def f_itemid(self, *args, **kw):
        return self._getinvrow(*args).itemID



    def f_typeid(self, *args):
        return self._getinvrow(*args).typeID




def GetCharacter(name, ignoreNotFound = False):
    result = sm.RemoteSvc('lookupSvc').LookupCharacters(name, 1)
    if result:
        cfg.eveowners.Prime([ each.characterID for each in result ])
        return result[0]
    try:
        return cfg.eveowners.Get(int(name))
    except:
        if ignoreNotFound:
            return 
        AsyncMessage('No such character', ('Character not found:<br>  %s' % name))
        raise UserError('IgnoreToTop')



def Act(what, set, p):
    c = sm.GetService(SERVICENAME).GetChannel()
    if not c:
        return 
    else:
        short = False
        if (what == 'gag'):
            mode = chat.CHTMODE_LISTENER
            verb = 'gagged'
        elif (what == 'ban'):
            if set:
                mode = chat.CHTMODE_DISALLOWED
            else:
                mode = chat.CHTMODE_NOTSPECIFIED
                short = True
            verb = 'banned'
        else:
            raise ValueError('unsupported action')
        if set:
            try:
                (char, reason, duration,) = p.Parse('ssi')
            except:
                (char, reason,) = p.Parse('ss')
                duration = 30
            if duration:
                until = (blue.os.GetTime() + (duration * MIN))
            else:
                until = None
        else:
            (char,) = p.Parse('s')
            reason = ''
            until = (blue.os.GetTime() - (30 * MIN))
        ret = GetCharacter(char)
        if short:
            sm.GetService('LSC').AccessControl(c.channelID, ret.characterID, mode)
        else:
            sm.GetService('LSC').AccessControl(c.channelID, ret.characterID, mode, until, reason)
        return ('%s has been %s%s' % (ret.characterName,
         ['un', ''][set],
         verb))



class SlashService(Service):
    __guid__ = ('svc.' + SERVICENAME)
    __notifyevents__ = ['ProcessRestartUI', 'OnExpoReset']
    __neocommenuitem__ = (('Slash Console', '09_07'),
     'Show',
     ROLEMASK_ELEVATEDPLAYER)
    __slashhook__ = True

    def __init__(self):
        self.patched = False
        self.aliases = {}
        self.macros = {}
        Service.__init__(self)



    def Run(self, memStream = None):
        self.state = SERVICE_START_PENDING
        try:
            self.wnd = None
            self.history = []
            self.historyPtr = -1
            self.lastslash = ''
            self.busy = self.aborted = False
            self.jobs = []
            self.LoadMacrosAndAliases()
            self.Clear()
        except:
            log.LogException()
            sys.exc_clear()
        self.state = SERVICE_RUNNING



    def GetMacros(self):
        self.LoadMacrosAndAliases()
        return self.macros.items()



    def LoadMacrosAndAliases(self):
        defaultMacros = {'GMH: Unload All': '/unload me all',
         'WM: Remove All Drones': '/unspawn range=500000 only=categoryDrone',
         'WM: Remove All Wrecks': '/unspawn range=500000 only=groupWreck',
         'WM: Remove CargoContainers': '/unspawn range=500000 only=groupCargoContainer',
         'WM: Remove SecureContainers': '/unspawn range=500000 only=groupSecureCargoContainer',
         'HEALSELF: Repair My Ship': '/heal',
         'HEALSELF: Repair My Modules': '/repairmodules',
         'GMH: Online My Modules': '/online me',
         'GML: Session Change 5sec': '/sessionchange 5'}
        self.aliases = self.LoadStuff(aliasFile)
        if (len(self.aliases) == 0):
            self.aliases = {}
        self.macros = self.LoadStuff(macroFile)
        self.macros.update(defaultMacros)



    def LoadStuff(self, fileName):
        targetFile = os.path.join(sm.StartService('insider').GetInsiderDir(), fileName)
        if not os.path.exists(targetFile):
            return {}
        else:
            d = {}
            lines = blue.win32.AtomicFileRead(targetFile)[0].replace('\r', '').replace('\x00', '').split('\n')
            for line in lines:
                (aliasName, comseq,) = line.split('=', 1)
                d[aliasName.strip()] = comseq.strip()

            return d



    def SaveStuff(self, thisDict, fileName):
        text = '\r\n'.join([ ('%s=%s' % (aliasName, comseq)) for (aliasName, comseq,) in thisDict.iteritems() ])
        targetFile = os.path.join(sm.StartService('insider').GetInsiderDir(), fileName)
        blue.win32.AtomicFileWrite(targetFile, text)



    def Stop(self, memStream = None):
        if (self.wnd and (not self.wnd.destroyed)):
            self.Hide()
        Service.Stop(self, memStream)



    def Show(self):
        self.wnd = wnd = sm.GetService('window').GetWindow('slashcon')
        if wnd:
            self.wnd.Maximize()
            return 
        self.wnd = wnd = sm.GetService('window').GetWindow('slashcon', create=1)
        wnd.DoClose = self.Hide
        wnd.SetWndIcon(None)
        wnd.SetTopparentHeight(0)
        wnd.SetCaption('Slash')
        wnd.SetMinSize([360, 224])
        main = uicls.Container(name='con', parent=uiutil.GetChild(wnd, 'main'), pos=(const.defaultPadding,
         const.defaultPadding,
         const.defaultPadding,
         const.defaultPadding))
        i = wnd.sr.i = uicls.Container(name='input', parent=main, align=uiconst.TOBOTTOM, height=20)
        c = uicls.Container(name='control', parent=main, align=uiconst.TOBOTTOM, height=24)
        o = uicls.Container(name='output', parent=main, align=uiconst.TOALL, pos=(0, 0, 0, 0))
        t = uicls.Label(text='Command: ', parent=i, align=uiconst.TOLEFT, color=None, state=uiconst.UI_DISABLED, singleline=1)
        input = wnd.sr.input = uicls.SinglelineEdit(name='slashCmd', parent=i, left=0, width=0, align=uiconst.TOALL)
        input.OnKeyDown = self.InputKey
        input.OnReturn = self.InputEnter
        buttons = [['Clear',
          self.Clear,
          None,
          81],
         ['Exec Last',
          self.ExLast,
          None,
          81],
         ['Exec Once',
          self.ExOnce,
          None,
          81],
         ['Exec Loop',
          self.ExLoop,
          None,
          81],
         ['Abort',
          self.ExAbort,
          None,
          81]]
        controls = uix.MakeBtnGroup(buttons, line=0)
        controls.align = uix.UI_ALRIGHT
        controls.width = ((69 * len(buttons)) + 10)
        controls.height = 16
        c.children.append(controls)

        def checker(cb):
            checked = cb.GetValue()
            settings.user.ui.Set('slashasync', checked)
            if checked:
                wnd.sr.async.hint = 'Non-Blocking: ON<br>Remote slash commands do not block the slash console.<br>Note: Local slash commands always block<br>Click to disable non-blocking mode.'
            else:
                wnd.sr.async.hint = 'Non-Blocking: OFF<br>Remote slash commands block the slash console until they finish.<br>Note: Local slash commands always block<br>Click to enable non-blocking mode.'
            cb.state = uix.UI_HIDDEN
            cb.state = uix.UI_NORMAL


        wnd.sr.async = uicls.Checkbox(text='Non-Blocking', parent=c, configName='slashasync', retval=0, checked=settings.user.ui.Get('slashasync', 0), callback=checker)
        checker(wnd.sr.async)
        wnd.sr.async.state = uix.UI_HIDDEN
        output = wnd.sr.output = uix.GetTextedit(self.outputcontents, where=o, readonly=1)
        output.autoScrollToBottom = 1
        uicore.registry.SetFocus(input)



    def Hide(self, *args):
        if self.wnd:
            self.slashcontents = self.wnd.sr.input.GetValue()
            self.wnd.Close()
        self.wnd = None



    def ProcessRestartUI(self):
        if self.wnd:
            self.Hide()
            self.Show()



    def OnExpoReset(self, trashPayload):
        self.cmd_exporeset('')



    def Clear(self, *args):
        header = ('<b><font color="#ffffff">Slash Console %s</font></b><br>Copyright \xa92006 <font color="#0099ff"><b>Jamie van den Berge</b></font> &lt;jamie@hlekkir.com&gt;<br>' % version)
        cmds = []
        for sname in svc.__all_svc__:
            s = getattr(svc, sname)
            if getattr(s, '__slashhook__', False):
                for m in dir(s):
                    if m.startswith('cmd_'):
                        cmds.append(m[4:])


        cmds.sort()
        self.outputcontents = (header + ('<br>Commands: <font color="#ffc000">%s</font><br>' % (', '.join(cmds) or 'None')))
        if self.aliases:
            cmds = self.aliases.keys()
            cmds.sort()
            self.outputcontents += ('Aliases: <font color="#00ffc0">%s</font><br>' % ', '.join(cmds))
        self.outputcontents += 'Note: Slash commands, extended commands and user-defined aliases all work both in chat and in console. Commands that require a chat channel cannot be run in the console.<br>'
        if self.wnd:
            self.wnd.sr.output.SetText(self.outputcontents)



    def Echo(self, text):
        self.outputcontents = ('%s%s' % (self.outputcontents, text))
        if self.wnd:
            self.wnd.sr.output.SetText(self.outputcontents)



    def InputKey(self, otherself, key, flag):
        if (key == uix.VK_UP):
            self.InputHistory(-1)
        elif (key == uix.VK_DOWN):
            self.InputHistory(1)
        else:
            otherself._OnKeyDown(key, flag)



    def InputHistory(self, offset):
        if self.history:
            self.historyPtr += offset
            if (self.historyPtr < -len(self.history)):
                self.historyPtr = -len(self.history)
                return 
            if (self.historyPtr >= 0):
                self.historyPtr = 0
                self.wnd.sr.input.SetValue('')
                return 
            self.wnd.sr.input.SetValue(self.history[self.historyPtr])



    def InputEnter(self, *args):
        try:
            self.lastslash = self.wnd.sr.input.GetValue()
        except:
            return 
        self.history.append(self.lastslash)
        self.historyPtr = 0
        self.wnd.sr.input.SetValue('')
        self.Ex(self.lastslash)



    def ExLoop(self, *args):
        try:
            cmd = self.wnd.sr.input.GetValue().strip()
        except:
            return 
        if (len(cmd) > 1):
            result = uix.QtyPopup(maxvalue=50, minvalue=1, caption='Looped Execution', label='', hint=('Specify number of times to execute:<br><br> %s<br><br>Note:<br>- Max. 50 iterations<br>- USE WITH CARE!' % cmd))
            if result:
                amount = result['qty']
                self.lastslash = cmd
                self.wnd.sr.input.SetValue('')
                self.Ex(cmd, amount)



    def ExLast(self, *args):
        if self.lastslash:
            self.Ex(self.lastslash)



    def ExOnce(self, *args):
        try:
            self.lastslash = self.wnd.sr.input.GetValue()
        except:
            return 
        self.Ex(self.lastslash)



    def ExAbort(self, *args):
        if (self.busy and (not self.aborted)):
            self.Echo('<font color="#ffff00">*** Aborting...</font><br>')
        self.aborted = True
        self.busy = False



    def Ex(self, this, count = 1):
        if not this:
            return 
        if (this[0] != '/'):
            this = ('/' + this)
        self.jobs.append((this, count))
        if self.busy:
            if (count > 0):
                self.Echo(('<font color="#ffff00">*** Queued(%d): %s</font><br>' % (count, this)))
            else:
                self.Echo(('<font color="#ffff00">*** Queued: %s</font><br>' % this))
            return 
        self.JobHandler()



    def JobHandler(self):
        try:
            self.aborted = False
            while (self.jobs and (not self.aborted)):
                (this, count,) = self.jobs.pop(0)
                isLoop = (count > 1)
                try:
                    self.busy = True
                    self.aborted = False
                    if isLoop:
                        self.Echo(('<font color="#ffff00">*** Loop started (%d iterations)</font><br>' % count))
                    while (count and (not self.aborted)):
                        self.Echo(('<font color="#00ffff">slash: %s</font><br>' % this))
                        try:
                            res = sm.GetService('slash').SlashCmd(this)
                            self.Echo(('<font color="#00ff55">slash result: %s</font><br>' % res))
                        except UserError, e:
                            if (e.args[0] in ('SlashError',)):
                                self.Echo(('<font color="#ff5500">slash error: %s</font><br>' % e.dict['reason']))
                                break
                            else:
                                self.Echo('<font color="#00ff55">slash result: None</font><br>')
                                raise UserError, e
                        count -= 1
                        blue.pyos.synchro.Sleep(100)

                    if isLoop:
                        if self.aborted:
                            reason = 'Aborted by user'
                        elif (count != 0):
                            reason = 'Error condition'
                        else:
                            reason = 'Batch completed'
                        self.Echo(('<font color="#ffff00">*** Loop terminated (%s)</font><br>' % reason))

                finally:
                    self.busy = False

            else:
                if self.jobs:
                    self.Echo('<font color="#ffff00">*** Slash command queue flushed</font><br>')
                    self.jobs = []


        finally:
            self.aborted = False




    def InitStuff(self):
        if hasattr(self, 'remoteCommandList'):
            return 
        try:
            sm.RemoteSvc('slash').SlashCmd('/')
        except UserError, e:
            try:
                msg = e.args[1]['reason']
                cmds = eval(msg.split(': ')[1])
                cmds.sort()
            except:
                cmds = []
                log.LogException()
                Message('Hmmm', "Unable to acquire server slash command list. Not particularly bad, just means command autocompletion won't work")
                sys.exc_clear()
            sys.exc_clear()
        self.remoteCommandList = cmds



    def MatchCmd(self, command):
        ret = []
        localMatch = False
        for cmd in self.aliases:
            if cmd.startswith(command):
                if (cmd == command):
                    return ([cmd], True)
                if (cmd not in ret):
                    ret.append(cmd)
                localMatch = True

        for cmd in self.remoteCommandList:
            if cmd.startswith(command):
                if (cmd == command):
                    return ([cmd], False)
                ret.append(cmd)

        for sname in svc.__all_svc__:
            s = getattr(svc, sname)
            if getattr(s, '__slashhook__', False):
                for f in s.__dict__:
                    if f.startswith(('cmd_' + command)):
                        cmd = f[4:]
                        if (cmd == command):
                            return ([cmd], True)
                        if (cmd not in ret):
                            ret.append(cmd)
                        localMatch = True


        return (ret, localMatch)



    def SlashCmd(self, command, fallThrough = True, isMacro = False):
        if command.startswith('//'):
            return sm.RemoteSvc('slash').SlashCmd(command[1:])
        try:
            return self._SlashCmd(command, fallThrough, isMacro)
        except Error, e:
            raise UserError('SlashError', {'reason': e})



    def _SlashCmd(self, txt, fallThrough, isMacro):
        if isMacro:
            self.InitStuff()
            command = None
        else:
            parts = txt.split(' ', 1)
            command = parts[0].strip().lower()
            if (len(parts) > 1):
                args = parts[1].strip()
            else:
                args = ''
            if (command[0] == '/'):
                command = command[1:]
            self.InitStuff()
            if (self.remoteCommandList is not None):
                (matches, hasLocal,) = self.MatchCmd(command)
                if hasLocal:
                    if (len(matches) == 1):
                        command = matches[0]
                    else:
                        raise Error(('%s is ambiguous. It resolves to multiple commands or aliases: %s' % (command, matches)))
        if (self.aliases.has_key(command) or isMacro):
            if isMacro:
                sequence = txt
            else:
                sequence = self.aliases[command]
            sequence = sequence.split(';')
            for line in sequence:
                line = line.strip()
                if line:
                    newline = []
                    inToken = False
                    p = param.ParamObject(txt)
                    for part in line.split(TOKEN):
                        if newline:
                            if (len(part) >= 1):
                                if (part[0] in '0123456789'):
                                    try:
                                        if ((len(part) >= 2) and (part[1] == '-')):
                                            part = (p[int(part[0]):] + part[2:])
                                        else:
                                            part = (p[int(part[0])] + part[1:])
                                    except param.Error:
                                        raise Error('Alias expected additional parameter(s)')
                                else:
                                    part = (TOKEN + part)
                        newline.append(part)

                    newline = (''.join(newline) % InlineFunctionResolver())
                    res = self.SlashCmd(newline)

            if (len(sequence) == 1):
                return res
            return 'Ok'
        try:
            args = (args % InlineFunctionResolver())
            commandLine = ((('/' + command) + ' ') + args)
        except Error:
            raise 
        except:
            log.LogException()
            raise Error('unexpected error resolving inline function')
        for sname in svc.__all_svc__:
            s = getattr(svc, sname)
            if getattr(s, '__slashhook__', False):
                slash = getattr(s, ('cmd_' + command), None)
                if slash:
                    try:
                        ret = getattr(sm.GetService(s.__guid__[4:]), ('cmd_' + command))(param.ParamObject(args))
                        if ret:
                            return ret
                        break
                    except param.Error, e:
                        if slash.__doc__:
                            raise Error(('usage: /%s %s' % (command, slash.__doc__.replace('%s', command))))
                        else:
                            raise Error(('/%s called with crap args and doesnt handle it properly' % command))

        if fallThrough:
            return sm.RemoteSvc('slash').SlashCmd(commandLine)



    def cmd_run(self, p):
        (filename,) = p.Parse('s')
        if not os.path.exists(filename):
            filename += '.txt'
            if not os.path.exists(filename):
                raise Error('file not found')
        for line in open(filename, 'r'):
            line = line.strip()
            if line.startswith('/'):
                self.SlashCmd(line)

        return 'Ok'



    def cmd_macromenu(self, p):
        return self.cmd_alias(p, True)



    def cmd_alias(self, p, isMacro = False):
        (what, rest,) = p.Parse('s?r')
        what = what.lower()
        if isMacro:
            thing = 'Macro'
            catalog = self.macros
            file = macroFile
        else:
            thing = 'Alias'
            catalog = self.aliases
            file = aliasFile
        if (what == 'add'):
            (what, aliasName, rest,) = p.Parse('ssr')
            for key in catalog.keys():
                if (key.lower() == aliasName.lower()):
                    action = 'modified'
                    break
            else:
                action = 'added'

            catalog[aliasName] = rest
            self.SaveStuff(catalog, file)
            return ('%s %s %s' % (thing,
             aliasName,
             action))
        if (what == 'del'):
            if not rest:
                raise param.Error
            aliasName = rest.lower().strip('"')
            for key in catalog.keys():
                if (key.lower() == aliasName):
                    del catalog[aliasName]
                    self.SaveStuff(catalog, file)
                    return ('%s %s deleted' % (thing, aliasName))
            else:
                raise Error(('No such %s: %s' % (thing, aliasName)))

        elif (what == 'list'):
            cmds = catalog.keys()
            cmds.sort()
            return ('%s: <font color="#00ffc0">%s</font><br>' % (thing, ', '.join(cmds)))
        for (key, item,) in catalog.iteritems():
            if (key.lower() == what.lower()):
                return ('%s %s is defined as: %s' % (thing,
                 what,
                 item))

        raise param.Error



    def cmd_loop(self, p):
        (count, rest,) = p.Parse('ir')
        if (count >= 10):
            if (count > 50):
                Message('Looped Execution', 'The number of iterations must be less than 50')
                return 
            ret = sm.GetService('gameui').MessageBox(title='Looped Execution', text=('You have specified %d iterations.<br>Slash commands looped this way cannot be aborted. Depending on the slash command, it can take a long time to complete or cause a high server load.<br>Continue?' % count), buttons=uix.OKCANCEL, icon=uix.WARNING)
            if ret:
                if (ret[0] in (uix.ID_CANCEL, uix.ID_CLOSE)):
                    return 
            for i in xrange(count):
                self.SlashCmd(rest)

            return 'Ok'



    def cmd_chtgag(self, p):
        return Act('gag', True, p)



    def cmd_chtungag(self, p):
        return Act('gag', False, p)



    def cmd_chtban(self, p):
        return Act('ban', True, p)



    def cmd_chtunban(self, p):
        return Act('ban', False, p)



    def cmd_whoami(self, p):
        roles = []
        for (k, v,) in globals().iteritems():
            if k.startswith('ROLE_'):
                if (type(v) in (int, long)):
                    if ((eve.session.role & v) and (v not in (ROLE_ANY,
                     ROLE_PLAYER,
                     ROLE_LOGIN))):
                        roles.append(k[5:])

        text = [('character: %s (%s)' % (eve.session.charid, cfg.eveowners.Get(eve.session.charid).name)),
         ('user: %s (type: %s)' % (eve.session.userid, eve.session.userType)),
         ('role: 0x%08X (%s)' % (eve.session.role, ', '.join(roles)))]
        AsyncMessage('Account Information', '<br>'.join(text))
        return 'Ok'



    def cmd_hop(self, p):
        if (eve.session.role & ROLE_WORLDMOD):
            return 
        else:
            (dist,) = p.Parse('i')
            if eve.session.stationid:
                raise Error("This obviously won't work in a station :)")
            bp = sm.GetService('michelle').GetBallpark()
            me = bp.GetBall(bp.ego)
            v = trinity.TriVector(me.vx, me.vy, me.vz)
            v.Normalize()
            v.Scale(dist)
            sm.RemoteSvc('slash').SlashCmd(('/tr me me offset=%d,%d,%d' % (int(v.x),
             int(v.y),
             int(v.z))))
            return 'Ok'



    def cmd_super(self, p):
        (target,) = p.Parse('s')
        if (target.lower() == 'me'):
            victim = 'you'
            target = 'me'
        else:
            char = GetCharacter(target)
            victim = (('"' + char.characterName) + '"')
            target = char.characterID
        Progress(('Making %s Leet' % victim), 'Please wait...', 0, 1)
        try:
            sm.RemoteSvc('slash').SlashCmd(('/giveskill %s all 5' % target))

        finally:
            Progress(('Making %s Leet' % victim), 'Done!', 1, 1)

        return 'Ok'



    def cmd_noob(self, p):
        (target,) = p.Parse('s')
        if (target.lower() == 'me'):
            victim = 'you'
            target = 'me'
        else:
            char = GetCharacter(target)
            victim = (('"' + char.characterName) + '"')
            target = char.characterID
        Progress(('Making %s n00b' % victim), 'Please wait...', 0, 1)
        try:
            sm.RemoteSvc('slash').SlashCmd(('/removeskill %s all' % target))

        finally:
            Progress(('Making %s n00b' % victim), 'Done!', 1, 1)

        return 'Ok'



    def cmd_bp(self, p):
        try:
            (name, rest,) = p.Parse('s?r')
            if (rest is None):
                rest = ''
        except param.Error:
            rest = ''
            try:
                (name,) = p.Parse('r')
            except param.Error:
                return 
        ret = self.AutoComplete(name, allowedCategories=[const.categoryBlueprint])
        if ret:
            return sm.RemoteSvc('slash').SlashCmd(('/bp %s %s' % (ret.typeID, rest)))



    def cmd_createitem(self, p):
        qty = 1
        try:
            (name,) = p.Parse('s')
        except param.Error:
            try:
                (name, qty,) = p.Parse('ss')
            except param.Error:
                try:
                    (name,) = p.Parse('r')
                except param.Error:
                    return 
        ret = self.AutoComplete(name)
        if ret:
            return sm.RemoteSvc('slash').SlashCmd(('/createitem %s %s' % (ret.typeID, qty)))



    def cmd_unload(self, p):
        try:
            (target, rest,) = p.Parse('s?r')
            if (rest is None):
                rest = ''
            else:
                rest = rest.lower().strip('"')
        except param.Error:
            return 
        else:
            if ((target.lower() != 'me') or ((rest == 'all') or rest.isdigit())):
                return 
            matches = {}
            for rec in eve.GetInventoryFromId(eve.session.shipid).List():
                if (rec.categoryID != const.categoryOwner):
                    rec = cfg.invtypes.Get(rec.typeID)
                    if (rest in rec.name.lower()):
                        matches[rec.name] = rec

        if not matches:
            return 
        matches = matches.items()
        matches.sort()
        if (len(matches) > 1):
            ret = uix.ListWnd(matches, listtype='generic', caption=('AutoComplete: %d types found' % len(matches)))
        else:
            ret = matches[0]
        if ret:
            return (sm.RemoteSvc('slash').SlashCmd(('/unload me %s' % ret[1].typeID)) or 'Ok')



    def cmd_loadcontainer(self, p):
        return self.cmd_fit(p, cmd='load', categories=None)



    def cmd_fit(self, p, cmd = 'fit', categories = -1):
        if (categories == -1):
            categories = [const.categoryModule,
             const.categoryDrone,
             const.categoryCharge]
        qty = ''
        try:
            (target, name, qty,) = p.Parse('sss')
        except param.Error:
            try:
                (target, name,) = p.Parse('ss')
            except param.Error:
                try:
                    (target, name,) = p.Parse('sr')
                except param.Error:
                    return 
        ret = self.AutoComplete(name, allowedCategories=categories)
        if ret:
            return sm.RemoteSvc('slash').SlashCmd(('/%s "%s" %s %s' % (cmd,
             target,
             ret.typeID,
             qty)))



    def cmd_entity(self, p):
        qty = 1
        try:
            (action, qty, name,) = p.Parse('sir')
        except param.Error:
            try:
                (action, name,) = p.Parse('sr')
            except param.Error:
                return 
        if (action.lower() != 'deploy'):
            return 
        ret = self.AutoComplete(name, allowedCategories=[const.categoryEntity])
        if ret:
            return (sm.RemoteSvc('slash').SlashCmd(('/entity deploy %s %s' % (qty, ret.typeID))) or 'Ok')



    def cmd_spawn(self, p):
        try:
            (name, rest,) = p.Parse('sr')
        except param.Error:
            try:
                (name,) = p.Parse('r')
                rest = ''
            except param.Error:
                return 
        ret = self.AutoComplete(name, allowedCategories=[const.categoryShip,
         const.categoryAsteroid,
         const.categoryDrone], allowedGroups=[const.groupLargeCollidableObject,
         const.groupCargoContainer,
         const.groupBiomass,
         const.groupComet,
         const.groupCloud,
         const.groupSentryGun])
        if ret:
            return sm.RemoteSvc('slash').SlashCmd(('/spawn %s %s' % (ret.typeID, rest)))



    def cmd_spawnn(self, p):
        try:
            (qty, deviation, name,) = p.Parse('ifr')
        except param.Error:
            return 
        ret = self.AutoComplete(name, allowedCategories=[const.categoryShip,
         const.categoryAsteroid,
         const.categoryDrone], allowedGroups=[const.groupLargeCollidableObject,
         const.groupCargoContainer,
         const.groupBiomass,
         const.groupComet,
         const.groupCloud,
         const.groupSentryGun])
        if ret:
            return (sm.RemoteSvc('slash').SlashCmd(('/spawnn %s %s %s' % (qty,
             deviation,
             ret.typeID))) or 'Ok')



    def cmd_giveskills(self, p):
        try:
            (target, skillname, level,) = p.Parse('ssi')
        except param.Error:
            try:
                level = None
                (target, skillname,) = p.Parse('sr')
            except param.Error:
                return 
        skillname = skillname.strip('"')
        if (skillname.lower() == 'all'):
            return 
        ret = self.AutoComplete(skillname, allowedCategories=[const.categorySkill])
        if ret:
            if (level is None):
                level = 5
            return (sm.RemoteSvc('slash').SlashCmd(('/giveskill "%s" %s %s' % (target,
             ret.typeID,
             level))) or 'Ok')



    def cmd_removeskills(self, p):
        try:
            level = None
            (target, skillname,) = p.Parse('sr')
        except param.Error:
            return 
        skillname = skillname.strip('"')
        if (skillname.lower() == 'all'):
            return 
        ret = self.AutoComplete(skillname, allowedCategories=[const.categorySkill])
        if ret:
            return (sm.RemoteSvc('slash').SlashCmd(('/removeskill "%s" %s' % (target, ret.typeID))) or 'Ok')



    def cmd_massfleetinvite(self, p):
        fleetSvc = sm.StartService('fleet')
        fleetSvc.CheckIsInFleet()
        members = fleetSvc.GetMembers()
        candidates = [ charID for charID in self.GetChannelUsers() if (charID not in members) ]
        if (eve.session.charid in candidates):
            candidates.remove(eve.session.charid)
        t = len(candidates)
        c = 0
        for charID in candidates:
            c += 1
            Progress('Inviting...', ('[%d/%d] %s' % (c,
             t,
             cfg.eveowners.Get(charID).ownerName)), c, t)
            try:
                fleetSvc.fleet.Invite(charID, None, None, None, True)
            except:
                pass

        Progress('Inviting...', 'Done!', 1, 1)
        return 'Ok'



    def cmd_masstransport(self, p):
        local = (('solarsystemid2', session.solarsystemid2))
        c = self.GetChannel()
        if ((not c) or (c.channelID == local)):
            return 
        else:
            l = sm.GetService('LSC').channels.get(local, None)
            if not l:
                return Error('No local channel?!')
            candidates = [ charID for charID in self.GetChannelUsers() if (charID not in l.memberList) ]
            if (session.charid in candidates):
                candidates.remove(session.charid)
            t = len(candidates)
            c = 0
            for charID in candidates:
                c += 1
                Progress('Transfering...', ('[%d/%d] %s' % (c,
                 t,
                 cfg.eveowners.Get(charID).ownerName)), c, t)
                sm.RemoteSvc('slash').SlashCmd(('/tr %d me noblock' % charID))

            Progress('Transfering...', 'All commands sent to the server', 1, 1)
            return 'Ok'



    def cmd_massstanding(self, p):
        try:
            (fromID, newStanding,) = p.Parse('sr')
        except:
            raise UserError('SlashError', {'reason': 'Provide from and the new standing value.'})
            sys.exc_clear()
            return 
        local = (('solarsystemid2', eve.session.solarsystemid2))
        c = self.GetChannel()
        if ((not c) or (c.channelID == local)):
            return 
        else:
            candidates = [ charID for charID in self.GetChannelUsers() ]
            if (eve.session.charid in candidates):
                candidates.remove(eve.session.charid)
            t = len(candidates)
            c = 0
            reason = ('Mass standing change by %s' % cfg.eveowners.Get(eve.session.charid).name)
            corps = sm.RemoteSvc('lookupSvc').LookupCorporations(fromID)
            corpIDs = [ each.corporationID for each in corps ]
            factions = sm.RemoteSvc('lookupSvc').LookupFactions(fromID)
            factionIDs = [ each.factionID for each in factions ]
            resultIDs = (corpIDs + factionIDs)
            resultList = [ (cfg.eveowners.Get(each).name,
             cfg.eveowners.Get(each).id,
             cfg.eveowners.Get(each).typeID) for each in resultIDs ]
            if not resultList:
                raise UserError('SlashError', {'reason': 'Unable to resolve corporation or faction name'})
                sys.exc_clear()
                return 
            ret = uix.ListWnd(resultList, caption=('AutoComplete: %d types found' % len(resultList)), ordered=1)
            if ret:
                if (eve.Message('CustomQuestion', {'header': 'Change standings?',
                 'question': ('Do you really want to change the standings towards %s for the characters in this channel?' % ret[0])}, uix.YESNO) == uix.ID_YES):
                    for charID in candidates:
                        c += 1
                        Progress('Setting standings...', ('[%d/%d] %s' % (c,
                         t,
                         cfg.eveowners.Get(charID).ownerName)), c, t)
                        sm.RemoteSvc('slash').SlashCmd(('/setstanding "%s" %d %s "%s"' % (ret[1],
                         charID,
                         newStanding,
                         reason)))

                    txt = ('Standings to %s set to %s for %d characters' % (ret[0],
                     newStanding,
                     c))
                else:
                    txt = ('Standings to %s left unmodified' % ret[0])
            else:
                txt = 'Standings left unmodified'
            Progress('Setting standings...', 'Done!', 1, 1)
            eve.Message('CustomNotify', {'notify': txt})
            return 'Ok'



    def cmd_exporeset(self, p):
        for var in ['expoStartLocation', 'expoFittingID']:
            if hasattr(prefs, var):
                continue
            raise UserError('SlashError', {'reason': 'expoStartLocation and expoFittingID must be configured in your prefs.ini'})
            continue

        shipItem = sm.RemoteSvc('slash').SlashCmd(('/exporeset %d %d %s' % (session.charid,
         prefs.expoStartLocation,
         prefs.expoFittingID)))
        if (shipItem is not None):
            sm.StartService('station').TryActivateShip(shipItem)
            return 'OK'



    def cmd_pos(self, p):
        try:
            (action, id,) = p.Parse('sr')
        except param.Error:
            try:
                action = p.Parse('s')
                bp = sm.GetService('michelle').GetBallpark()
                for ballID in bp.balls.keys():
                    try:
                        item = bp.GetInvItem(ballID)
                        if (item.groupID in (const.groupControlTower)):
                            id = item.itemID
                            action = action[0]
                    except:
                        pass

            except:
                pass
        if not id:
            return 
        else:
            if (action.lower() != 'fuel'):
                return 
            try:
                id = int(id)
            except ValueError:
                eve.Message('CustomInfo', {'info': "This must be called as either '/pos fuel' or '/pos fuel itemID'"})
                return 'ok'
            else:
                resourcesPerHour = []
                reinforcedResourcesPerHour = []
                totalFuelVolumePerCycle = float()
                starbaseCharters = []
                chosenTower = eve.GetInventoryFromId(id)
                secStatus = sm.GetService('map').GetSecurityStatus(eve.session.locationid)
                for item in cfg.invtypes:
                    if (item.groupID in (const.groupLease)):
                        starbaseCharters.append(item.typeID)

                for tower in sm.RemoteSvc('posMgr').GetControlTowerFuelRequirements():
                    if (tower.controlTowerTypeID == chosenTower.GetTypeID()):
                        if (tower.resourceTypeID == const.typeStrontiumClathrates):
                            reinforcedResourcesPerHour.append((tower.resourceTypeID,
                             tower.quantity,
                             cfg.invtypes.Get(tower.resourceTypeID).volume))
                        elif (secStatus < 0.4):
                            if (tower.resourceTypeID not in starbaseCharters):
                                resourcesPerHour.append((tower.resourceTypeID,
                                 tower.quantity,
                                 cfg.invtypes.Get(tower.resourceTypeID).volume))
                        else:
                            resourcesPerHour.append((tower.resourceTypeID,
                             tower.quantity,
                             cfg.invtypes.Get(tower.resourceTypeID).volume))

                for (resourceTypeID, amountPerCycle, resourceVolume,) in resourcesPerHour:
                    totalFuelVolumePerCycle += int((amountPerCycle * resourceVolume))

                totalFuelVolumePerCycle += 1
                strontVolume = reinforcedResourcesPerHour[0][2]
                fuelBay = chosenTower.GetCapacity()
                strontBay = chosenTower.GetCapacity(flag=const.flagSecondaryStorage)
                fuelCycles = int((fuelBay.capacity / totalFuelVolumePerCycle))
                strontCycles = int((strontBay.capacity / strontVolume))
                if (eve.session.role & ROLE_WORLDMOD):
                    addList = []
                    for (commodity, amountPerCycle, resourceVolume,) in reinforcedResourcesPerHour:
                        sm.GetService('slash').SlashCmd(('crea %d %s' % (commodity, strontCycles)))
                        for cargo in eve.GetInventoryFromId(eve.session.shipid).ListCargo():
                            if (cargo.typeID == commodity):
                                addList.append(cargo.itemID)


                    eve.GetInventoryFromId(id).MultiAdd(addList, eve.session.shipid, flag=const.flagSecondaryStorage)
                    addList = []
                    for (fuel, amountPerCycle, resourceVolume,) in resourcesPerHour:
                        sm.GetService('slash').SlashCmd(('crea %d %s' % (fuel, int((fuelCycles * amountPerCycle)))))
                        for cargo in eve.GetInventoryFromId(eve.session.shipid).ListCargo():
                            if ((cargo.typeID == fuel) and (cargo.stacksize == (fuelCycles * amountPerCycle))):
                                addList.append(cargo.itemID)


                    eve.GetInventoryFromId(id).MultiAdd(addList, eve.session.shipid, flag=const.flagNone)
                    return 'Ok'
                shipType = const.typeBHMegaCargoShip
                oldShipItemID = eve.session.shipid
                newShipItemID = int()
                if (eve.GetInventoryFromId(int(eve.session.shipid)).GetTypeID() != shipType):
                    newShipItemID = sm.RemoteSvc('slash').SlashCmd(('/spawn %d' % shipType))
                    ship = sm.StartService('gameui').GetShipAccess()
                    if ship:
                        sm.StartService('sessionMgr').PerformSessionChange('board', ship.Board, newShipItemID, oldShipItemID)
                blue.pyos.synchro.Sleep(5000)
                addList = []
                for (commodity, amountPerCycle, resourceVolume,) in reinforcedResourcesPerHour:
                    sm.GetService('slash').SlashCmd(('load me %d %s' % (commodity, strontCycles)))
                    for cargo in eve.GetInventoryFromId(eve.session.shipid).ListCargo():
                        if (cargo.typeID == commodity):
                            addList.append(cargo.itemID)


                eve.GetInventoryFromId(id).MultiAdd(addList, eve.session.shipid, flag=const.flagSecondaryStorage)
                addList = []
                for (fuel, amountPerCycle, resourceVolume,) in resourcesPerHour:
                    sm.GetService('slash').SlashCmd(('load me %d %s' % (fuel, int((fuelCycles * amountPerCycle)))))
                    for cargo in eve.GetInventoryFromId(eve.session.shipid).ListCargo():
                        if ((cargo.typeID == fuel) and (cargo.stacksize == (fuelCycles * amountPerCycle))):
                            addList.append(cargo.itemID)


            eve.GetInventoryFromId(id).MultiAdd(addList, eve.session.shipid, flag=const.flagNone)
            if (blue.os.GetTime() <= eve.session.nextSessionChange):
                ms = (1000 + ((1000L * (eve.session.nextSessionChange - blue.os.GetTime())) / 10000000L))
                blue.pyos.synchro.Sleep(ms)
                if newShipItemID:
                    ship = sm.StartService('gameui').GetShipAccess()
                    if ship:
                        sm.StartService('sessionMgr').PerformSessionChange('board', ship.Board, oldShipItemID, newShipItemID)
                        sm.GetService('slash').SlashCmd(('heal %d 0' % newShipItemID))
                        blue.pyos.synchro.Sleep(5000)
                        sm.GetService('insider').HealRemove(const.groupWreck)
            return 'Ok'



    def cmd_ammo(self, p):
        try:
            typeID = p.Parse('s')[0]
        except:
            raise UserError('SlashError', {'reason': 'Usage: /ammo typeID'})
        shipInv = eve.GetInventoryFromId(session.shipid)
        for row in shipInv.ListHardwareModules():
            shipInv.ReplaceCharges(row.flagID, None, forceRemove=True)

        if ('typeID' == 'clear'):
            return 'Ok'
        else:
            typeID = int(typeID)
            blue.pyos.synchro.Sleep(1000)
            cmds = []
            stateMgr = sm.GetService('godma').GetStateManager()
            charge = cfg.invtypes.Get(typeID)
            chargeSize = sm.GetService('godma').GetTypeAttribute(typeID, const.attributeChargeSize)
            for row in shipInv.ListHardwareModules():
                ma = stateMgr.GetType(row.typeID)
                num = int((ma.capacity / charge.volume))
                if (num > 0):
                    cmds.append(('/fit me %d %d flag=%d' % (typeID,
                     num,
                     row.flagID)))

            uthread.parallel([ (sm.GetService('slash').SlashCmd, (cmd)) for cmd in cmds ])
            return 'Ok'



    def cmd_sessionchangetimer(self, p):
        try:
            secs = p.Parse('i')[0]
            prefs.sessionChangeTimer = min(max(5, secs), 60)
        except:
            pass
        eve.Message('CustomNotify', {'notify': ('Session-Change Timer is <b>%s</b> seconds' % prefs.GetValue('sessionChangeTimer', 30))})
        return 'Ok'



    def cmd_recustomize(self, p):
        if sm.GetService('cc').NoExistingCustomization():
            return 1
        else:
            sm.GetService('gameui').GoCharacterCreationCurrentCharacter()
            return 1



    def MatchTypes(self, name, allowedCategories = None, allowedGroups = None, smart = True):
        name = name.strip('"')
        if not hasattr(self, 'typeIDByName'):
            d = self.typeIDByName = {}
            for line in cfg.invtypes:
                d[line.typeName.lower()] = line.typeID


        def _filter(rec):
            if allowedCategories:
                if (rec.categoryID in allowedCategories):
                    return True
                if allowedGroups:
                    if (rec.groupID in allowedGroups):
                        return True
                elif allowedGroups:
                    if (rec.groupID in allowedGroups):
                        return True
                else:
                    return True
                return False


        if (smart and (name.lower() in self.typeIDByName)):
            rec = cfg.invtypes.Get(self.typeIDByName[name.lower()])
            return [(rec.name, rec)]
        else:
            if name.isdigit():
                rec = cfg.invtypes.GetIfExists(int(name))
                if rec:
                    if (1 or _filter(rec)):
                        return [(rec.name, rec)]
                    raise Error(("Type '%s' is not in the list of allowed types for this command" % rec.name))
            else:
                name = name.lower().strip('"')
            if (len(name) < 3):
                raise UserError('SlashError', {'reason': 'Autocompletion requires 3 or more characters'})
            matches = []
            count = 0
            for (typeName, typeID,) in self.typeIDByName.iteritems():
                if not (count % 500):
                    blue.pyos.synchro.Yield()
                count += 1
                if (name in typeName):
                    rec = cfg.invtypes.Get(typeID)
                    if _filter(rec):
                        matches.append((rec.name, rec))

            return matches



    def AutoComplete(self, name, allowedCategories = None, allowedGroups = None):
        matches = self.MatchTypes(name, allowedCategories, allowedGroups)
        if not matches:
            return 
        else:
            if (len(matches) == 1):
                ret = matches[0]
            else:
                matches.sort()
                ret = uix.ListWnd(matches, listtype='generic', caption=('AutoComplete: %d types found' % len(matches)))
            if not ret:
                raise Error('Cancelled')
            return ret[1]



    def GetChannel(self):
        f = sys._getframe().f_back
        while f:
            loc = f.f_locals
            if (loc.has_key('self') and isinstance(loc['self'], form.LSCChannel)):
                return loc['self']
            f = f.f_back

        raise Error('This function can only be used in a chat channel')



    def GetChannelUsers(self):
        c = self.GetChannel()
        return [ each.charID for each in c.userlist.sr.entries ]



exports = {'slash.Error': Error}

